package com.demo.topics;

import java.io.IOException;
import java.util.List;

import android.accounts.AccountManager;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

import com.demo.topics.util.SessionManager;
import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.android.gms.common.AccountPicker;
import com.google.android.youtube.player.YouTubeStandalonePlayer;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.YouTubeScopes;
import com.google.api.services.youtube.model.Playlist;
import com.google.api.services.youtube.model.PlaylistListResponse;

/**
 * This fragment represents the view for playlists the user has created.
 * <p/>
 * This view needs to do a few things:
 * <ol>
 * <li>Check to see if we have a token or not</li>
 * <li>If we have a token, assume we are logged in and show our playlists</li>
 * <li>If we do NOT have a token, assume we are not logged in. Show a view that
 * prompts for login instead</li>
 * </ol>
 *
 * @author Ikai Lan <ikai@google.com>
 */
public class PlaylistFragment extends ListFragment implements SessionManager.SessionListener {
    private static final String TAG = PlaylistFragment.class.getSimpleName();

    private View mLoginScreen;
    private View mPlaylistScreen;

    private SessionManager mSessionManager;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_show_playlists,
                container, false);
        // TODO: Check for Play services. If it's not present, show a view
        // explaining this an disable this functionality.

        mSessionManager = SessionManager.getInstance(getActivity());
        mSessionManager.addSessionListener(this);

        // Find the views and stash them in local variables
        mLoginScreen = view.findViewById(R.id.login_screen);
        mPlaylistScreen = view.findViewById(R.id.playlist_screen);

        // Check to see if we are logged in or not and show the appropriate
        // screen
        if (mSessionManager.isUserLoggedIn()) {
            fetchAndShowPlaylists();
        } else {
            showLoginScreen();
        }

        // Bind listeners
        Button loginButton = (Button) view.findViewById(R.id.login_button);
        loginButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mSessionManager.startAuthFlow();
            }
        });

        return view;
    }

    /**
     * This method changes the visibility of the login screen to GONE and the
     * playlist screen to VISIBLE. Typically called after a login event. This
     * function is the opposite of {@link #showLoginScreen()}.
     */
    private void showPlaylists() {
        mLoginScreen.setVisibility(View.GONE);
        mPlaylistScreen.setVisibility(View.VISIBLE);
    }

    /**
     * This method changes the visibility of the login screen to VISIBLE and the
     * playlist screen to GONE. Typically called after a logout event. This is
     * the opposite of {@link #showPlaylists()}.
     */
    private void showLoginScreen() {
        mLoginScreen.setVisibility(View.VISIBLE);
        mPlaylistScreen.setVisibility(View.GONE);
    }

    /**
     * This method logs the user out and clears the playlist screen, swapping for the login screen.
     * Generally, this is invoked from the MainActivity.
     */
    protected void logoutAndClearPlaylists() {
        String authToken = mSessionManager.getAuthToken();

        if (mSessionManager.logout()) {
            // TODO should SessionManager own this functionality? It will need to store the context if it does
            GoogleAuthUtil.invalidateToken(getActivity(), authToken);
            setListAdapter(null);
            showLoginScreen();
        } else {
            // TODO if we fail to log out, show an error or some other recovery mechanism
        }
    }


    /**
     * This method shows the playlist UI, then reads the auth token from shared
     * preferences and makes an API call. If the token is not present or the
     * token is invalid, starts the token fetch flow again. A calling method
     * recovering from this exception show call
     * {@link SessionManager#fetchAuthToken(String)}.
     */
    public void fetchAndShowPlaylists() {
        Log.d(TAG, "called fetchAndShowPlaylists");
        String authToken = mSessionManager.getAuthToken();
        if (authToken == null) {
            Log.w(TAG,
                    "Auth token not saved in shared preferences. Starting auth flow.");
            // If we don't find a token, start the full flow to get an auth
            // token.
            // There really isn't any way we should end up here unless there's
            // programmer error or user clears their cache somehow in between
            // calls
            mSessionManager.startAuthFlow();
        } else {
            new FetchPlaylistTask().execute(authToken);
        }
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        Playlist playlist = (Playlist) l.getAdapter().getItem(position);

        Intent playlistIntent = new Intent(getActivity(), VideoPlayerActivity.class);
        playlistIntent.putExtra(VideoPlayerActivity.PLAYLIST_ID, playlist.getId());
        playlistIntent.putExtra(VideoPlayerActivity.PLAYLIST_TITLE, playlist.getSnippet().getTitle());
        startActivity(playlistIntent);
    }

    /**
     * Show the playlists after a successful login.
     *
     * @param accountName
     * @param authToken
     */
    @Override
    public void onLoggedIn(String accountName, String authToken) {
        fetchAndShowPlaylists();
    }

    @Override
    public void onLoggedOut() {
        // TODO: Change the UI to show that we have logged out
        Log.d(TAG, "Playlist fragment received message that we have logged out");
    }

    // Async task classes and methods below

    /**
     * This asynchronous task handles making API calls to the YouTube API,
     * updating the UI once the task has completed. TODO error handling on the
     * API call. Perhaps in publish progress?
     * <p/>
     * Note: you should only ever call this with one authToken, or else this
     * will throw an {@link IllegalArgumentException}.
     *
     * @author Ikai Lan <ikai@google.com>
     */
    private class FetchPlaylistTask extends
            AsyncTask<String, Void, List<Playlist>> {

        ProgressDialog mProgressDialog;

        @Override
        protected void onPreExecute() {
            mProgressDialog = ProgressDialog.show(getActivity(), "",
                    getResources().getString(R.string.loading_your_playlists), true);

        }

        @Override
        protected List<Playlist> doInBackground(String... authTokens) {
            if (authTokens.length != 1) {
                throw new IllegalArgumentException(
                        "doInBackround should only be called with one auth token");
            }

            String authToken = authTokens[0];
            // TODO: implement this
            // If we do find a token but it is expired, throw an exception

            // If we find a token and it is valid, show the list and render the
            // view
            // TODO: this code is also likely to be used in Search Results to
            // determine playlists to save content to. We'll have to extract the
            // comment code out to a helper class.

            // Before this code works, these steps must be followed:
            // https://developers.google.com/+/mobile/android/getting-started#step_1_enable_the_google_api
            GoogleCredential credential = new GoogleCredential();
            credential.setAccessToken(authToken);

            HttpTransport httpTransport = new NetHttpTransport();
            JsonFactory jsonFactory = new JacksonFactory();

            YouTube yt = new YouTube.Builder(httpTransport, jsonFactory,
                    credential).setApplicationName("YouTubeMobileCodeLab/0.1")
                    .build();

            try {
                PlaylistListResponse response = yt.playlists()
                        .list("id,snippet").setMine(true).execute();
                return response.getItems();
            } catch (IOException e) {
                Log.e(TAG,
                        "Got IOException fetching playlists: " + e.getMessage());
                Log.e(TAG, e.getMessage());
                Log.e(TAG, "Using authToken: " + authToken);
                // TODO update the UI via publish progress and cancel the job
                if (e.getMessage().startsWith("401")) {
                    Log.i(TAG, "invalidating the token");
                    // THIS IS A HACK
                    GoogleAuthUtil.invalidateToken(getActivity(), authToken);
                }

            }
            return null;
        }

        /**
         * Update the UI with the user's playlists.
         */
        @Override
        protected void onPostExecute(List<Playlist> playlists) {
            mProgressDialog.dismiss();
            if (playlists != null) {
                for (Playlist playlist : playlists) {
                    Log.d(TAG, "Got playlist: " + playlist.getSnippet().getTitle());
                }
                PlaylistAdapter adapter = new PlaylistAdapter(getActivity(),
                        R.layout.playlist_list_row, playlists);
                getListView().setAdapter(adapter);
                showPlaylists();
            }
        }
    }

    /**
     * Adapter class to handle rendering of playlist results from the YouTube
     * API.
     *
     * @author Ikai Lan <ikai@google.com>
     */
    private static class PlaylistAdapter extends ArrayAdapter<Playlist> {

        private Activity mContext;
        private int mLayoutId;

        /**
         * Constructor for Playlist adapter. We shouldn't use this unless we
         * already have a playlist, so this accepts a {@link List} of
         * {@link Playlist} objects that we've received from the YouTube client
         * library.
         *
         * @param context            the calling context
         * @param textViewResourceId a layout ID to render
         * @param playlists          a list of playlists to render
         */
        public PlaylistAdapter(Context context, int textViewResourceId,
                               List<Playlist> playlists) {
            super(context, textViewResourceId, playlists);
            mContext = (Activity) context;
            mLayoutId = textViewResourceId;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = convertView;
            if (v == null) {
                LayoutInflater inflater = mContext.getLayoutInflater();
                v = inflater.inflate(mLayoutId, null);
            }

            if (position < getCount()) {
                Playlist playlist = getItem(position);
                TextView title = (TextView) v.findViewById(R.id.playlist_title);
                TextView description = (TextView) v
                        .findViewById(R.id.playlist_description);

                title.setText(playlist.getSnippet().getTitle());
                description.setText(playlist.getSnippet().getDescription());
            }

            return v;
        }

    }
}
